package peace.gui.controls.scroll
{
import flash.display.DisplayObject;
import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.geom.Point;
import flash.utils.Timer;

import peace.data.Interval;
import peace.gui.controls.buttons.PushButton;
/**
* 
*/	
[Event(name="scrollX", type="peace.gui.controls.scroll.ScrollEvent")]
/**
* 
*/
[Event(name="scrollY", type="peace.gui.controls.scroll.ScrollEvent")]
/**
* 
*/
[Event(name="contentSizeChanged", type="peace.gui.controls.scroll.ScrollEvent")]
/**
 * 
 */
public class Scrollbar extends Sprite
{
	/**
	 * шаг скроллинга по кнопкам 
	 * 
	 * @default 10
	 */	
	public var scrollStep:int = 10;
	/**
	 * изменяется ли размер бегунка? 
	 * 
	 * @default true
	 */	
	public var scaleRunner:Boolean = true;
	/**
	 * минимальный размер раннера, чтобы не уходил в точку
	 */
	protected static const MIN_RUNNER_SIZE:int = 20;
	/**
	 * 
	 */
	protected static const EVENT_DELAY:uint = 50;
	/**
	 * 
	 */
	protected static const WAITING_DELAY:uint = 500;
	/**
	 * 
	 */	
	protected var _content:DisplayObject=null;
	/**
	 * 
	 */
	protected var cashedContentSize:Number;
	/**
	 * 
	 */
	protected var _size:Number;
	/**
	 * 
	 */
	protected var _isHorizontal:Boolean;
	/**
	 * 
	 */
	protected var skin:Skin=null;
	/**
	 * 
	 */
	protected var timer:Timer = new Timer(EVENT_DELAY);
	/**
	 * 
	 */
	protected var upPressed:Boolean = false;
	/**
	 * 
	 */
	protected var downPressed:Boolean = false;
	/**
	 * 
	 */
	protected var runnerDragged:Boolean = false;
	/**
	 * 
	 */
	protected var runnerDragPoint:Point = new Point();
	/**
	* Доступные расстояния/интервалы движения контента и трека
	*/	
	protected var contentIntervalPixels:Number;
	protected var trackIntervalPixels:Number;
	/**
	 * 
	 */
	protected var stageSubscribed:Boolean = false;
	/**
	 * 
	 * @param size
	 * @param isHorizontal
	 * 
	 */	
	public function Scrollbar (size:Number=200, isHorizontal:Boolean=false)
	{
		_size = size;
		_isHorizontal = isHorizontal;

		timer.addEventListener(TimerEvent.TIMER, onTimer, false, 0, true);
		addEventListener(Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true);
		addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true);
	}
	/**
	 * 
	 * Как правило перерисовываем при изменении размеров контента
	 * 
	 */	
	public function redraw ():void
	{
		if (_content==null)	return;
		
		if (!_isHorizontal)
		{
			if(scaleRunner)
			{
				skin.runner.height = new Interval(MIN_RUNNER_SIZE, skin.track.height).crop(skin.track.height * (_size/_content.height));
			}
			trackIntervalPixels = skin.track.height-skin.runner.height;
		}
		else
		{
			if(scaleRunner)
			{
				skin.runner.width = new Interval(MIN_RUNNER_SIZE, skin.track.width).crop(skin.track.width * (_size/_content.width));
			}
			trackIntervalPixels = skin.track.width-skin.runner.width;
		}
		contentIntervalPixels = _isHorizontal ? _content.width-_size : _content.height-_size;
		if (contentIntervalPixels < 0) contentIntervalPixels = 0;
		if (trackIntervalPixels < 0) trackIntervalPixels = 0;
		
		scrollTo(scrollPosition);
		
		// кешируем новый размер контента
		cashedContentSize = _isHorizontal ? _content.width : _content.height;
	}
	/**
	 * 
	 * @param up InteractiveObject
	 * @param down InteractiveObject
	 * @param runner InteractiveObject
	 * @param track InteractiveObject
	 * 
	 */	
	public function setSkin (	up:InteractiveObject, down:InteractiveObject, 
								runner:InteractiveObject, track:InteractiveObject):void
	{
		/**
		* На лету пока скин не меняем (пока не нужно)
		*/		
		if (skin==null)
		{
			// оборачиваем в PushButton чтобы не учитывать скейлинг
			if (runner && !(runner is PushButton))
			{
				runner = new PushButton(runner);
			}
			if (track && !(track is PushButton))
			{
				track = new PushButton(track);
			}

			skin = new Skin(_isHorizontal, up, down, runner, track);
			applySkin();
		}
	}
	/**
	 * 
	 * @return 
	 * 
	 */	
	public function get content ():DisplayObject
	{
		return _content;
	}
	/**
	 * 
	 * @param d DisplayObject
	 * 
	 */
	public function set content (d:DisplayObject):void
	{
		if (_content==null) // первый раз
		{
			if (skin==null)
			{
				skin = new Skin(_isHorizontal);
				applySkin();
			}
			_content = d;
		}
		else
		{
			_content = d;
		}
		cashedContentSize = _isHorizontal ? _content.width : _content.height;
		construct();

		if (! _isHorizontal)
		{
			x = _content.x+_content.width;
			y = _content.y;
			contentIntervalPixels = _content.height-_size;
		}
		else
		{
			y = _content.y+_content.height;
			x = _content.x;
			contentIntervalPixels = _content.width-_size;
		}
		if (contentIntervalPixels < 0) contentIntervalPixels = 0;
		
		scrollTo(0);
	}
	/**
	 * 
	 * @return Number
	 * 
	 */	
	public function get scrollPosition ():Number
	{
		if (_content==null)
			return 0;
		return !_isHorizontal ? y-_content.y : x-_content.x;
	}
	/**
	 * 
	 * @param n
	 * 
	 */
	public function set scrollPosition (n:Number):void
	{
		scrollTo (n);
	}
	/**
	 * 
	 * @return 
	 * 
	 */	
	public function get size ():Number
	{
		return _size;
	}
	/**
	 * Меняем размер контрола
	 */
	public function set size (n:Number):void
	{
		_size = n;

		if (_content==null)	return;

		if (skin != null)
		{
			timer.reset();
			runnerDragged = false;
			
			construct();
			contentIntervalPixels = _isHorizontal ? _content.width-_size : _content.height-_size;
			if (contentIntervalPixels < 0) contentIntervalPixels = 0;
			
			scrollTo (scrollPosition);
		}
	}
	/**
	 * Принудительно обрабатываем изменение размера контента
	 * 
	 */	
	public function forcedOnContentResize ():void
	{
		onEnterFrame(null);
	}
	/**
	 * Расставляем скины по местам
	 */	
	protected function construct():void
	{
		if (!_isHorizontal)
		{
			skin.track.y = skin.up.height;	
			skin.runner.y = skin.up.height;
			skin.down.y = _size-skin.down.height;
			skin.track.height = _size-skin.up.height-skin.down.height;
			if(scaleRunner)
			{
				skin.runner.height = new Interval(MIN_RUNNER_SIZE, skin.track.height).crop(skin.track.height * (_size/_content.height));
			}

			trackIntervalPixels = skin.track.height-skin.runner.height;
		}
		else
		{
			skin.track.x = skin.up.width;
			skin.runner.x = skin.up.width;
			skin.down.x = _size-skin.down.width;
			skin.track.width = _size-skin.up.width-skin.down.width;
			if(scaleRunner)
			{
				skin.runner.width = new Interval(MIN_RUNNER_SIZE, skin.track.width).crop(skin.track.width * (_size/_content.width));
			}

			trackIntervalPixels = skin.track.width-skin.runner.width;
		}
		if (trackIntervalPixels < 0) trackIntervalPixels = 0;
	}
	/**
	 * 
	 * 
	 */	
	protected function applySkin():void
	{
		addChild(skin.track);
		skin.track.addEventListener(MouseEvent.MOUSE_DOWN, onTrack, false, 0, true);
		addChild(skin.runner);
		skin.runner.addEventListener(MouseEvent.MOUSE_DOWN, onRunner, false, 0, true);
		if (stage != null && !stageSubscribed)
		{
			onAddedToStage();
		}
		addChild(skin.up);
		skin.up.addEventListener(MouseEvent.MOUSE_DOWN, onUp, false, 0, true);
		addChild(skin.down);
		skin.down.addEventListener(MouseEvent.MOUSE_DOWN, onDown, false, 0, true);
	}
	/**
	 * 
	 * @param n
	 * 
	 */	
	protected function scrollTo (n:Number):void
	{
		if (_content==null)	return;
		if (n < 0 || isNaN(n)) 
			n = 0;
		else if (n > contentIntervalPixels)
			n = contentIntervalPixels;
		
		var delta:Number = n*(trackIntervalPixels/contentIntervalPixels);
		if (isNaN(delta)) delta = 0;
		
		if (!_isHorizontal)
		{
			_content.y = y-n;	
			skin.runner.y = skin.up.height + delta;
			
			dispatchEvent(new ScrollEvent(ScrollEvent.SCROLL_Y, scrollPosition));
		}
		else
		{
			_content.x = x-n;	
			skin.runner.x = skin.up.width + delta;
			
			dispatchEvent(new ScrollEvent(ScrollEvent.SCROLL_X, scrollPosition));
		}
	}
	/**
	 * 
	 * @param e
	 * 
	 */	
	protected function onAddedToStage (e:Event=null):void
	{
		if (skin != null)
		{
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onRunner, false, 0, true);
			stage.addEventListener(MouseEvent.MOUSE_UP, onRunner, false, 0, true);
			stage.addEventListener(MouseEvent.MOUSE_UP, onUp, false, 0, true);
			stage.addEventListener(MouseEvent.MOUSE_UP, onDown, false, 0, true);
			stageSubscribed = true;
		}
	}

	/**
	 * Обработчики событий мышки
	 */	
	private function onTrack (e:MouseEvent):void
	{
		if (e.type==MouseEvent.MOUSE_DOWN)
		{
			var delta:Number = _isHorizontal ? skin.track.mouseX-(skin.runner.width/2) : skin.track.mouseY-(skin.runner.height/2);
			scrollTo(delta*contentIntervalPixels/trackIntervalPixels);
		}
	}
	
	private function onRunner (e:MouseEvent):void
	{
		if (e.type==MouseEvent.MOUSE_DOWN)
		{
			runnerDragPoint.x = skin.runner.mouseX;
			runnerDragPoint.y = skin.runner.mouseY;
			runnerDragged = true;
		}
		else if (e.type==MouseEvent.MOUSE_UP)
		{
			runnerDragged = false;
		}
		else if (e.type==MouseEvent.MOUSE_MOVE && runnerDragged)
		{
			var delta:Number = 	_isHorizontal
								? mouseX - runnerDragPoint.x - skin.up.width
								: mouseY - runnerDragPoint.y - skin.up.height;
			scrollTo(delta*contentIntervalPixels/trackIntervalPixels);
			e.updateAfterEvent();
		}
	}
	
	private function onUp (e:MouseEvent):void
	{
		if (e.type==MouseEvent.MOUSE_DOWN)
		{
			scrollTo(scrollPosition-scrollStep);
			upPressed = true;
			if (! timer.running)
			{
				timer.delay = WAITING_DELAY;
				timer.start();
			}
		}
		else if (e.type==MouseEvent.MOUSE_UP)
		{
			timer.reset();
			upPressed = false;
		}
	}
	
	private function onDown (e:MouseEvent):void
	{
		if (e.type==MouseEvent.MOUSE_DOWN)
		{
			scrollTo(scrollPosition+scrollStep);
			downPressed = true;
			if (! timer.running)
			{
				timer.delay = WAITING_DELAY;
				timer.start();
			}
		}
		else if (e.type==MouseEvent.MOUSE_UP)
		{
			timer.reset();
			downPressed = false;
		}
	}
	
	private function onTimer (e:TimerEvent):void
	{
		if (timer.currentCount==1)
		{
			timer.delay = EVENT_DELAY;
		}
		else if (timer.currentCount > 1)
		{
			if (upPressed)
			{
				scrollTo(scrollPosition-(timer.currentCount-1)*scrollStep);
			}
			else if (downPressed)
			{
				scrollTo(scrollPosition+(timer.currentCount-1)*scrollStep);
			}
			e.updateAfterEvent();
		}
	}
	
	private function onEnterFrame (e:Event):void
	{
		if (_content==null)	return;

		if (	(_isHorizontal && _content.width != cashedContentSize) 
			|| 	(! _isHorizontal && _content.height != cashedContentSize))
		{
			redraw();
			dispatchEvent(new ScrollEvent(ScrollEvent.CONTENT_SIZE_CHANGED,null));
		}
	}
	
}
}


import peace.gui.controls.buttons.PushButton;
import flash.display.InteractiveObject;
import flash.display.Shape;
import flash.display.Graphics;

internal class Skin
{
	private var defaultUpSkin:PushButton=null;
	private var defaultDownSkin:PushButton=null;
	private var defaultRunnerSkin:PushButton=null;
	private var defaultTrackSkin:PushButton=null;
	
	private var _up:InteractiveObject;
	private var _down:InteractiveObject;
	private var _runner:InteractiveObject;
	private var _track:InteractiveObject;
	
	private var _isHorizontal:Boolean;
	/**
	* Ширина скроллбара по умолчанию, для дефолтного скина
	*/	
	private static const WIDTH:uint = 14;
	
	public function get up():InteractiveObject
	{
		return (_up==null) ? getDefaultUpSkin() : _up;	
	}
	public function get down():InteractiveObject
	{
		return (_down==null) ? getDefaultDownSkin() : _down;	
	}
	public function get runner():InteractiveObject
	{
		return (_runner==null) ? getDefaultRunnerSkin() : _runner;	
	}
	public function get track():InteractiveObject
	{
		return (_track==null) ? getDefaultTrackSkin() : _track;	
	}
	
	public function Skin (	isHorizontal:Boolean=false,
							up:InteractiveObject=null, down:InteractiveObject=null, 
							runner:InteractiveObject=null, track:InteractiveObject=null)
	{
		_isHorizontal = isHorizontal;
		
		_up = up;
		_down = down;
		_runner = runner;
		_track = track;
		
	}
	
	private function getDefaultUpSkin ():InteractiveObject
	{
		if (defaultUpSkin==null)
		{
			var g:Shape = new Shape();
			g.graphics.lineStyle(0, 0);
			g.graphics.beginFill(0xcccccc);
			g.graphics.drawRect(0,0,WIDTH,WIDTH);
			g.graphics.lineStyle(0,0,0);
			g.graphics.beginFill(0);
			if (_isHorizontal)
			{
				g.graphics.moveTo(WIDTH*2/3,2);
				g.graphics.lineTo(WIDTH/3,WIDTH/2);
				g.graphics.lineTo(WIDTH*2/3,WIDTH-2);
				g.graphics.lineTo(WIDTH*2/3,2);
			}
			else
			{
				g.graphics.moveTo(2,WIDTH*2/3);
				g.graphics.lineTo(WIDTH/2,WIDTH/3);
				g.graphics.lineTo(WIDTH-2,WIDTH*2/3);
				g.graphics.lineTo(2,WIDTH*2/3);
			}
			defaultUpSkin = new PushButton(g);
		}
		return defaultUpSkin;
	}
	private function getDefaultDownSkin ():InteractiveObject
	{
		if (defaultDownSkin==null)
		{
			var g:Shape = new Shape();
			g.graphics.lineStyle(0, 0);
			g.graphics.beginFill(0xcccccc);
			g.graphics.drawRect(0,0,WIDTH,WIDTH);
			g.graphics.lineStyle(0,0,0);
			g.graphics.beginFill(0);
			if (_isHorizontal)
			{
				g.graphics.moveTo(WIDTH/3,2);
				g.graphics.lineTo(WIDTH*2/3,WIDTH/2);
				g.graphics.lineTo(WIDTH/3,WIDTH-2);
				g.graphics.lineTo(WIDTH/3,2);
			}
			else
			{
				g.graphics.moveTo(2,WIDTH/3);
				g.graphics.lineTo(WIDTH/2,WIDTH*2/3);
				g.graphics.lineTo(WIDTH-2,WIDTH/3);
				g.graphics.lineTo(2,WIDTH/3);
			}
			defaultDownSkin = new PushButton(g);
		}
		return defaultDownSkin;
	}
	private function getDefaultRunnerSkin ():InteractiveObject
	{
		if (defaultRunnerSkin==null)
		{
			var g:Shape = new Shape();
			g.graphics.lineStyle(0, 0);
			g.graphics.beginFill(0xcccccc);
			g.graphics.drawRect(0,0,WIDTH,WIDTH);
			defaultRunnerSkin = new PushButton(g);
		}
		return defaultRunnerSkin;
	}
	private function getDefaultTrackSkin ():InteractiveObject
	{
		if (defaultTrackSkin==null)
		{
			var g:Shape = new Shape();
			g.graphics.lineStyle(0,0,0);
			g.graphics.beginFill(0xf0f0f0);
			g.graphics.drawRect(0,0,WIDTH,WIDTH);
			defaultTrackSkin = new PushButton(g);
		}
		return defaultTrackSkin;
	}
}